﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Visuals;
using DiscoveryLogic.Common.Utilities;

namespace DiscoveryLogic.Analytics.Modules.Diagrams.Renders
{
    public class ForceDirected : IGraphRenderer
    {
        private int _MaxIteration = 5000;
        private bool _IsStopped;
        public event OnFinishedGraphRendering GraphRenderFinished;

        public bool IsStopped
        {
            get { return _IsStopped; }
            set
            {
                if (this._IsStopped != value)
                {
                    _IsStopped = value;
                    if (this.GraphRenderFinished != null)
                        this.GraphRenderFinished();
                }
            }
        }
        private double _DampingFactor = 0.9;

        #region Implementation of IGraphRenderer

        /// <summary>
        /// 
        /// </summary>
        /// <param name="vertices"></param>
        /// <param name="edges"></param>
        /// <param name="netVisual"></param>
        /// <param name="constraintRect"></param>
        /// <param name="iteration"></param>
        /// <param name="attractForceConst"></param>
        /// <param name="repellingForceConst"></param>
        public void UpdatePositions(List<Vertice> vertices, List<Edge> edges, 
            NetworkVisual netVisual, Rect constraintRect,
            int iteration, double attractForceConst, double repellingForceConst)
        {
            if (iteration > this._MaxIteration)
            {
                this.IsStopped = true;
                return;
            }
            double repulsionRange = Math.Min(constraintRect.Width, constraintRect.Height) / 4;
            double maxDistance = 0;
            double minDistance = double.PositiveInfinity;
            foreach (Edge e in edges)
            {
                if (e.Length > maxDistance)
                    maxDistance = e.Length;
                if (e.Length < minDistance)
                    minDistance = e.Length;
            }

            double scaleFactor = maxDistance * 2 / Math.Max(constraintRect.Width, constraintRect.Height);
            double minVerticeWidth = 20;
            Dictionary<Type, Dictionary<Type, double>> distanceScales =
                new Dictionary<Type, Dictionary<Type, double>>();
            if(netVisual.EdgeVisuals !=null && netVisual.EdgeVisuals.Count>0)
            {
                foreach(EntityType et in netVisual.EdgeVisuals.Keys)
                {
                    Dictionary<Type, double> toDistanceScales = new Dictionary<Type, double>();
                    if (distanceScales.ContainsKey(EntityTypeUtil.GetType(et)))
                        toDistanceScales = distanceScales[EntityTypeUtil.GetType(et)];
                    foreach(EntityType et2 in netVisual.EdgeVisuals[et].Keys)
                    {
                        if (toDistanceScales.ContainsKey(EntityTypeUtil.GetType(et2)))
                            toDistanceScales[EntityTypeUtil.GetType(et2)] = netVisual.EdgeVisuals[et][et2].DistanceScale;
                        else
                            toDistanceScales.Add(EntityTypeUtil.GetType(et2), netVisual.EdgeVisuals[et][et2].DistanceScale);
                    }
                    if (distanceScales.ContainsKey(EntityTypeUtil.GetType(et)))
                        distanceScales[EntityTypeUtil.GetType(et)] = toDistanceScales;
                    else
                        distanceScales.Add(EntityTypeUtil.GetType(et), toDistanceScales);
                }
            }
            

            Dictionary<Guid, Vertice> vs = new Dictionary<Guid, Vertice>();
            foreach (Vertice v in vertices)
            {
                vs.Add(v.ID, v);
            }

            int numOfMoves = 0;

            foreach (Vertice v in vertices)
            {
                Vector newForce = new Vector();
                if (v.CanMove)
                {
                    // attraction 
                    foreach (Edge e in edges)
                    {
                        double distance = (e.Length > 0) ? e.Length / scaleFactor : 10;
                        if(distanceScales.ContainsKey(e.Tag.FromType) && 
                            distanceScales[e.Tag.FromType].ContainsKey(e.Tag.ToType))
                        {
                            double distanceScale = distanceScales[e.Tag.FromType][e.Tag.ToType];
                            double minEdgeLength = minVerticeWidth * distanceScale;
                            if (minDistance / scaleFactor > minEdgeLength)
                            {
                                double scaleFactor2 = minDistance / minEdgeLength;
                                distance = (e.Length > 0) ? e.Length/scaleFactor2 : 10;
                            }
                        }
                        
                        if (e.VerticeID1 == v.ID)
                        {
                            Vertice v2 = vs[e.VerticeID2];
                            double d = (v.Position - v2.Position).Length;
                            d = d - (v.Control.ImageSize.Width + v2.Control.ImageSize.Width)/2;
                            if (d <= 0)
                                continue;

                            double springForce = attractForceConst * Math.Log(d / distance);
                            double fx = springForce * (v2.Position.X - v.Position.X) / d;
                            if (fx < -repulsionRange)
                                fx = -repulsionRange;
                            else if (fx > repulsionRange)
                                fx = repulsionRange;
                            double fy = springForce * (v2.Position.Y - v.Position.Y) / d;
                            if (fy < -repulsionRange)
                                fy = -repulsionRange;
                            else if (fy > repulsionRange)
                                fy = repulsionRange;

                            newForce += new Vector(fx, fy);
                        }
                        else if (e.VerticeID2 == v.ID)
                        {
                            Vertice v2 = vs[e.VerticeID1];
                            double d = (v.Position - v2.Position).Length;
                            d = d - (v.Control.ImageSize.Width + v2.Control.ImageSize.Width) / 2;
                            if (d <= 0)
                                continue;
                            
                            double springForce = attractForceConst * Math.Log(d / distance);
                            double fx = springForce * (v2.Position.X - v.Position.X) / d;
                            if (fx < -repulsionRange)
                                fx = -repulsionRange;
                            else if (fx > repulsionRange)
                                fx = repulsionRange;
                            double fy = springForce * (v2.Position.Y - v.Position.Y) / d;
                            if (fy < -repulsionRange)
                                fy = -repulsionRange;
                            else if (fy > repulsionRange)
                                fy = repulsionRange;

                            newForce += new Vector(fx, fy);
                        }
                    }

                    // repelling
                    foreach (Vertice v2 in vertices)
                    {
                        if (v.ID != v2.ID)
                        {
                            double d = (v.Position - v2.Position).Length;
                            if (d == 0)
                            {
                                d = 10;
                            }
                            if (d > 0 && d < repulsionRange + v.Control.ImageSize.Width / 2 + v2.Control.ImageSize.Width / 2)
                            {
                                double fx = Math.Pow(repellingForceConst/d, 2)*((v.Position.X - v2.Position.X)/d);
                                if (fx < -repulsionRange)
                                    fx = -repulsionRange;
                                else if (fx > repulsionRange)
                                    fx = repulsionRange;
                                double fy = Math.Pow(repellingForceConst/d, 2)*((v.Position.Y - v2.Position.Y)/d);
                                if (fy < -repulsionRange)
                                    fy = -repulsionRange;
                                else if (fy > repulsionRange)
                                    fy = repulsionRange;

                                newForce += new Vector(fx, fy);
                            }
                        }
                    }
                    if (newForce.X > 3 || newForce.Y > 3)
                    {
                        numOfMoves++;
                    }

                    double dampingFactor = ((double)this._MaxIteration - iteration) / this._MaxIteration;
                    //v.Force = newForce * dampingFactor;
                    //v.Velocity += v.Force / 5;
                    //Vector deltaPos = v.Velocity * 0.1;
                    Point forceDrag = newForce.ToPoint();
                    if (forceDrag.X > repulsionRange / 2)
                        forceDrag.X = repulsionRange / 2;
                    if (forceDrag.Y > repulsionRange / 2)
                        forceDrag.Y = repulsionRange / 2;
                    double rx = 10;
                    double ry = 10;
                    if (v.Control != null)
                    {
                        rx = v.Control.ActualWidth / 2;
                        ry = v.Control.ActualHeight / 2;
                    }
                    rx *= dampingFactor;
                    ry *= dampingFactor;
                    double x = v.Position.X + forceDrag.X;
                    double y = v.Position.Y + forceDrag.Y;
                    if (x < constraintRect.Left + rx)
                        x = constraintRect.Left + rx;
                    if (x > constraintRect.Right - rx)
                        x = constraintRect.Right - rx;
                    if (y < constraintRect.Top + ry)
                        y = constraintRect.Top + ry;
                    if (y > constraintRect.Bottom - ry)
                        y = constraintRect.Bottom - ry;
                    v.Position = new Vector(x, y);
                }
            }


            if (numOfMoves == 0)
            {
                this.IsStopped = true;
            }
        }

        /// <summary>
        /// draw edges and also labels
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="netVisual"></param>
        /// <param name="vertices"></param>
        /// <param name="edges"></param>
        public void RenderEdges(DrawingContext dc, NetworkVisual netVisual,
            List<Vertice> vertices, List<Edge> edges)
        {
            Dictionary<Guid, Vertice> vs = new Dictionary<Guid, Vertice>();
            foreach (Vertice v in vertices)
            {
                vs.Add(v.ID, v);
            }
            foreach (Edge e in edges)
            {
                Vertice v1 = vs[e.VerticeID1];
                Vertice v2 = vs[e.VerticeID2];
                EdgeVisual ev = null;
                if (netVisual.EdgeVisuals.ContainsKey(v1.Tag.EntityType) &&
                    netVisual.EdgeVisuals[v1.Tag.EntityType].ContainsKey(v2.Tag.EntityType))
                {
                    ev = netVisual.EdgeVisuals[v1.Tag.EntityType][v2.Tag.EntityType];
                }
                Point p1 = v1.Position.ToPoint();
                Point p2 = v2.Position.ToPoint();
                if (v2.Control != null)
                {
                    double d = (v1.Position - v2.Position).Length;
                    double dx = Math.Abs(p1.X - p2.X) / d * v2.Control.ImageSize.Width / 2;
                    double dy = Math.Abs(p1.Y - p2.Y) / d * v2.Control.ImageSize.Height / 2;
                    if (p1.X < p2.X)
                        p2.X -= dx;
                    else
                        p2.X += dx;
                    if (p1.Y < p2.Y)
                        p2.Y -= dy;
                    else
                        p2.Y += dy;
                }

                Color edgeColor = e.PenColor;
                Pen pen = new Pen(new SolidColorBrush(e.PenColor), e.Thickness);
                if (e.LineStyle == EdgeLineStyle.Dashed)
                    pen.DashStyle = DashStyles.Dash;
                if (ev != null)
                {
                    edgeColor = ev.Color;
                    pen.Brush = new SolidColorBrush(ev.Color);
                    if (ev.ThicknessMeasure == EdgeThicknessMeasure.Fixed)
                        pen.Thickness = ev.Thickness;
                    else if (ev.ThicknessMeasure == EdgeThicknessMeasure.Distance)
                    {
                        pen.Thickness = Math.Max(1, 10 - e.Tag.Distance);
                    }
                    else if(ev.ThicknessMeasure==EdgeThicknessMeasure.Aggregates)
                    {
                        pen.Thickness = e.Tag.Aggregates * 2;
                    }
                    if (ev.LineStyle == EdgeLineStyle.Dashed)
                        pen.DashStyle = DashStyles.Dash;
                }
                if (v1.Disabled || v2.Disabled)
                    pen.Brush = new SolidColorBrush(Color.FromArgb(10, edgeColor.R, edgeColor.G, edgeColor.B));
                if (e is Arc)
                {
                    this.DrawArrow(dc, pen, p1, p2);
                    //pen.EndLineCap = PenLineCap.Triangle;
                    //dc.DrawLine(pen, p1, p2);
                }
                else
                {
                    pen.EndLineCap = PenLineCap.Round;
                    dc.DrawLine(pen, p1, p2);
                }
            }
        }

        private void DrawArrow(DrawingContext dc, Pen pen, Point p1, Point p2)
        {
            Point[] aptArrowHead = new Point[3];
            aptArrowHead[0] = p2;
            DrawingVector vecLine = new DrawingVector(p2.X - p1.X, p2.Y - p1.Y);// build the line vector
            DrawingVector vecLeft = new DrawingVector(-vecLine[1], vecLine[0]);// build the arrow base vector - normal to the line
            double arrowWidth = 8f;
            double arrowTheta = 0.5f;
            // setup remaining arrow head points
            double lineLength = vecLine.Length;
            double th = arrowWidth / (2.0f * lineLength);
            double ta = arrowWidth / (2.0f * ((double)Math.Tan(arrowTheta / 2.0f)) * lineLength);

            // find the base of the arrow
            Point pBase = new Point(aptArrowHead[0].X + -ta * vecLine[0], aptArrowHead[0].Y + -ta * vecLine[1]); //base of the arrow

            // build the points on the sides of the arrow
            aptArrowHead[1] = new Point(pBase.X + th * vecLeft[0], pBase.Y + th * vecLeft[1]);
            aptArrowHead[2] = new Point(pBase.X + -th * vecLeft[0], pBase.Y + -th * vecLeft[1]);

            dc.DrawLine(pen, p1, pBase); //master line

            dc.DrawLine(pen, aptArrowHead[0], aptArrowHead[1]);
            dc.DrawLine(pen, aptArrowHead[1], aptArrowHead[2]);
            dc.DrawLine(pen, aptArrowHead[2], aptArrowHead[0]);
        }
        #endregion
    }
}
